import axios from 'axios'

export class AxiosRetry {
    // 维护一个 promise
    private fetchNewTokenPromise: Promise<any> | null = null

    // 一些必须的配置
    private baseUrl: string
    private url: string
    private getRefreshToken: () => string | null
    private unauthorizedCode: string | number
    private onSuccess: (res: any) => any
    private onError: (err?: any) => any

    constructor({
        baseUrl,
        url,
        getRefreshToken,
        unauthorizedCode = 401,
        onSuccess,
        onError
    }: {
        baseUrl: string
        url: string
        getRefreshToken: () => string | null
        unauthorizedCode?: number | string
        onSuccess: (res: any) => any
        onError: (err: any) => any
    }) {
        this.baseUrl = baseUrl
        this.url = url
        this.getRefreshToken = getRefreshToken
        this.unauthorizedCode = unauthorizedCode
        this.onSuccess = onSuccess
        this.onError = onError
    }

    requestWrapper<T>(request: () => Promise<T>): Promise<T> {
        return new Promise<T>((resolve, reject) => {
            // 先把请求函数保存下来
            const requestFn = request
            return request()
                .then(resolve)
                .catch((err) => {
                    if (
                        err?.status === this.unauthorizedCode &&
                        !(err?.config?.url === this.url) // 不处理获取 token 的请求
                    ) {
                        if (!this.fetchNewTokenPromise) {
                            this.fetchNewTokenPromise = this.fetchNewToken()
                        }
                        return this.fetchNewTokenPromise
                            .then(() => requestFn().then(resolve, reject)) // 获取 token 成功后，重新执行请求
                            .catch(console.error)
                            .finally(() => (this.fetchNewTokenPromise = null))
                    } else {
                        reject(err)
                    }
                })
        })
    }

    // 获取 token 的函数
    fetchNewToken() {
        return axios.get(this.url, {
            baseURL: this.baseUrl,
            headers: {
                Authorization: this.getRefreshToken()
            }
        })
        .then(this.onSuccess)
        .catch(() => {
            this.onError()
            throw new Error('Failed to fetch a new token')
        })
    }
}
